from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QGridLayout, QApplication, QComboBox, QPushButton, QLabel, QPlainTextEdit, QMessageBox
from PyQt5.QtGui import QFont
from sys import argv, exit

import serial
import serial.tools.list_ports

import re


def print_me(thing: bytes):
    return ''.join(
        f'\\x{byte:02x}'
        for byte in thing)

class Binary:
    """
        自定义进制转化
    """
    @staticmethod
    def Hex2Dex(e_hex):
        """
        十六进制转换十进制
        :param e_hex:
        :return:
        """
        return int(e_hex, 16)

    @staticmethod
    def Hex2Bin(e_hex):
        """
        十六进制转换二进制
        :param e_hex:
        :return:
        """
        return bin(int(e_hex, 16))

    @staticmethod
    def Dex2Bin(e_dex):
        """
        十进制转换二进制
        :param e_dex:
        :return:
        """
        return bin(e_dex)

class CRC:
    """
     CRC验证
    """
    def __init__(self):
        self.Binary = Binary()

    def CRC16(self, hex_num):
        """
        CRC16校验

        :param hex_num:
        :return:
        """
        crc = '0xffff'
        crc16 = '0xA001'
        # test = '01 06 00 00 00 00'
        test = hex_num.split(' ')
        print(test)

        crc = self.Binary.Hex2Dex(crc)  # 十进制
        crc16 = self.Binary.Hex2Dex(crc16)  # 十进制
        for i in test:
            temp = '0x' + i
            # 亦或前十进制准备
            temp = self.Binary.Hex2Dex(temp)  # 十进制
            # 亦或
            crc ^= temp  # 十进制
            for i in range(8):
                if self.Binary.Dex2Bin(crc)[-1] == '0':
                    crc >>= 1
                elif self.Binary.Dex2Bin(crc)[-1] == '1':
                    crc >>= 1
                    crc ^= crc16
                # print('crc_D:{}\ncrc_B:{}'.format(crc, self.Binary.Dex2Bin(crc)))

        crc = str(hex(crc))
        if len(crc) == 5:
            crc = "0" + crc[2:]
            crc_H = crc[:2]
            crc_L = crc[2:]
        else:
            crc_H = crc[2:4]
            crc_L = crc[-2:]

        return crc, crc_H, crc_L

class Modbus_Channel(QWidget):

    def __init__(self):
        super(Modbus_Channel, self).__init__()
        self.setWindowTitle("串口通信")
        self.setWindowModality(Qt.WindowModal)

        self.resize(800, 600)
        self.setStyleSheet('background-color: #ffffff;')

        self.setup_ui()

        self.CRC = CRC()

    def setup_ui(self):
        
        self.VBoxLayout_ALL = QHBoxLayout() # 总布局
        
        self.GridLayout_L = QGridLayout()   # 左侧布局

        self.GridLayout_R = QGridLayout()   # 右侧布局
        
        self.VBoxLayout_ALL.addLayout(self.GridLayout_L)
        self.VBoxLayout_ALL.addLayout(self.GridLayout_R)

        font = QFont()
        font.setFamily('SimHei')
        font.setPixelSize(14)

        self.COM_PORT_LABEL = QLabel("串口号:")
        self.COM_PORT_LABEL.setFont(font)
        self.COM_SHOW = QComboBox()
        self.COM_SHOW.setFixedWidth(80)
        self.COM_SHOW.setFont(font)
        self.COM_DETECT = QPushButton("串口检测")
        self.COM_DETECT.setFixedWidth(100)
        self.COM_DETECT.setFont(font)
        self.GridLayout_L.addWidget(self.COM_PORT_LABEL, 1, 1, 1, 1)
        self.GridLayout_L.addWidget(self.COM_SHOW, 1, 2, 1, 1)
        self.GridLayout_L.addWidget(self.COM_DETECT, 1, 3, 1, 1)

        self.BRAUD_RATE_LABEL = QLabel("波特率")
        self.BRAUD_RATE_LABEL.setFont(font)
        BRAUD_RATE = ['9600', '19200', '38400', '57600', '115200']
        self.BRAUD_RATE = QComboBox()
        self.BRAUD_RATE.setFixedWidth(80)
        self.BRAUD_RATE.setFont(font)
        self.BRAUD_RATE.addItems(BRAUD_RATE)
        self.OPEN_COM = QPushButton("打开串口")
        self.OPEN_COM.setFixedWidth(100)
        self.OPEN_COM.setFont(font)

        self.GridLayout_L.addWidget(self.BRAUD_RATE_LABEL, 2, 1, 1, 1)
        self.GridLayout_L.addWidget(self.BRAUD_RATE, 2, 2, 1, 1)
        self.GridLayout_L.addWidget(self.OPEN_COM, 2, 3, 1, 1)


        self.WRITE_SINGAL_COMMAND = QPushButton("单圈")
        self.WRITE_MULTIP_COMMAND = QPushButton("多圈")
        self.WRITE_COILS_COMMAND = QPushButton("圈数")
        self.WRITE_ZERO_COMMAND = QPushButton("零点")
        self.WRITE_BRAUD_COMMAND = QPushButton("波特率")
        self.WRITE_DIRECTION_COMMAND = QPushButton("旋转方向")


        self.WRITE_SINGAL_COMMAND.setFixedWidth(60)
        self.WRITE_MULTIP_COMMAND.setFixedWidth(60)
        self.WRITE_COILS_COMMAND.setFixedWidth(60)
        self.WRITE_ZERO_COMMAND.setFixedWidth(60)
        self.WRITE_BRAUD_COMMAND.setFixedWidth(60)
        self.WRITE_DIRECTION_COMMAND.setFixedWidth(60)

        self.WRITE_SINGAL_COMMAND.setFont(font)
        self.WRITE_MULTIP_COMMAND.setFont(font)
        self.WRITE_COILS_COMMAND.setFont(font)
        self.WRITE_ZERO_COMMAND.setFont(font)
        self.WRITE_BRAUD_COMMAND.setFont(font)
        self.WRITE_DIRECTION_COMMAND.setFont(font)

        self.GridLayout_L.addWidget(self.WRITE_BRAUD_COMMAND, 3, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_ZERO_COMMAND, 4, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_DIRECTION_COMMAND, 5, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_SINGAL_COMMAND, 6, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_MULTIP_COMMAND, 7, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_COILS_COMMAND, 8, 1, 1, 3, Qt.AlignCenter)


        self.SEND_DATA_LABEL = QLabel("发送指令:")
        self.SEND_DATA_LABEL.setFont(font)

        self.SEND_DATA = QLabel()
        self.SEND_DATA.setFont(font)
        self.SEND_DATA.setFixedWidth(150)

        self.GridLayout_L.addWidget(self.SEND_DATA_LABEL, 9, 1, 1, 1)
        self.GridLayout_L.addWidget(self.SEND_DATA, 9, 2, 1, 2)

        self.RETURN_DATA_LABEL = QLabel("返回数据:")
        self.RETURN_DATA_LABEL.setFont(font)

        self.RETURN_DATA = QLabel()
        self.RETURN_DATA.setFont(font)
        self.RETURN_DATA.setFixedWidth(150)
        self.GridLayout_L.addWidget(self.RETURN_DATA_LABEL, 10, 1, 1, 1)
        self.GridLayout_L.addWidget(self.RETURN_DATA, 10, 2, 1, 2)


        self.SINGAL_VALUE_LABEL = QLabel("单圈值：")
        self.SINGAL_VALUE_LABEL.setFont(font)
        self.SINGAL_VALUE = QLabel()
        self.SINGAL_VALUE.setFixedWidth(85)
        self.SINGAL_VALUE.setFont(font)
        self.GridLayout_L.addWidget(self.SINGAL_VALUE_LABEL, 11, 1, 1, 1)
        self.GridLayout_L.addWidget(self.SINGAL_VALUE, 11, 2, 1, 2)

        self.MUTIPLE_VALUE_LABEL = QLabel("多圈值：")
        self.MUTIPLE_VALUE_LABEL.setFont(font)
        self.MUTIPLE_VALUE = QLabel()
        self.MUTIPLE_VALUE.setFixedWidth(85)
        self.MUTIPLE_VALUE.setFont(font)
        self.GridLayout_L.addWidget(self.MUTIPLE_VALUE_LABEL, 12, 1, 1, 1)
        self.GridLayout_L.addWidget(self.MUTIPLE_VALUE, 12, 2, 1, 2)

        self.COIL_VALUE_LABEL = QLabel("圈数值：")
        self.COIL_VALUE_LABEL.setFont(font)
        self.COIL_VALUE = QLabel()
        self.COIL_VALUE.setFixedWidth(85)
        self.COIL_VALUE.setFont(font)
        self.GridLayout_L.addWidget(self.COIL_VALUE_LABEL, 13, 1, 1, 1)
        self.GridLayout_L.addWidget(self.COIL_VALUE, 13, 2, 1, 2)

        self.AGLE_VALUE_LABEL = QLabel("角度：")
        self.AGLE_VALUE_LABEL.setFont(font)
        self.AGLE_VALUE = QLabel()
        self.AGLE_VALUE.setFixedWidth(85)
        self.AGLE_VALUE.setFont(font)
        self.GridLayout_L.addWidget(self.AGLE_VALUE_LABEL, 14, 1, 1, 1)
        self.GridLayout_L.addWidget(self.AGLE_VALUE, 14, 2, 1, 2)

        self.TEXT_EDIT = QPlainTextEdit()
        self.TEXT_EDIT.setFixedWidth(600)
        self.TEXT_EDIT.setFixedHeight(600)
        self.GridLayout_R.addWidget(self.TEXT_EDIT)


        self.setLayout(self.VBoxLayout_ALL)

        self.COM_DETECT.clicked.connect(self.COM_DETECT_FUNCTION)
        self.COUNT = 0
        self.OPEN_COM.clicked.connect(self.OPEN_CLOSE_TRANS)

        self.WRITE_SINGAL_COMMAND.clicked.connect(self.SIGNAL_VALUE_FUNCTION)
        self.WRITE_MULTIP_COMMAND.clicked.connect(self.MUTIPLE_VALUE_FUNCTION)
        self.WRITE_COILS_COMMAND.clicked.connect(self.COIL_VALUE_FUNCTION)
        self.WRITE_ZERO_COMMAND.clicked.connect(self.SET_ZERO_VALUE_FUNCTION)
        self.WRITE_BRAUD_COMMAND.clicked.connect(self.BAUD_VALUE_FUNCTION)
        self.WRITE_DIRECTION_COMMAND.clicked.connect(self.SET_DIRECTION_FUNCTION)

    def COM_DETECT_FUNCTION(self):
        port_list = list(serial.tools.list_ports.comports())
        port_name = []
        for port in port_list :
            port_name.append(port[0])

        self.COM_SHOW.addItems(port_name)

    def SERIAL_OPEN(self):
        port_name = self.COM_SHOW.currentText()
        braud_rate = int(self.BRAUD_RATE.currentText())
        try :
            ser = serial.Serial(port=port_name,
                                 baudrate=braud_rate,
                                 parity=serial.PARITY_NONE,
                                 bytesize=8,
                                 stopbits=1,
                                 timeout=1)
            return ser
        except serial.serialutil.SerialException as f:
            print(f)
            return None

    def OPEN_CLOSE_TRANS(self):

        if self.COUNT % 2 == 0 :
            self.OPEN_COM.setText("打开串口")
            ser = self.SERIAL_OPEN()
            # ser.open()
            print(ser)
        else :
            self.OPEN_COM.setText("关闭串口")
            ser = self.SERIAL_OPEN()
            ser.close()
            print(ser)
        self.COUNT += 1

        return ser
    '''发送报文'''
    def SEND_MESSAGE(self, data):
        ser = self.SERIAL_OPEN()
        sendbytes = 0
        if ser != None :
            # 生成CRC16校验码
            crc, crc_H, crc_L = self.CRC.CRC16(data)
            # 生成完整报文
            sendbytes = data + ' ' + crc_L + ' ' + crc_H + " " + '\r\n'
            self.SEND_DATA.setText(sendbytes)  # 显示到发送数据label
            Byte_HEX = bytes.fromhex(sendbytes)
            ser.write(Byte_HEX)  # 发送给编码器
            RECEIVE_RX_DECODE = ser.readline()
            RECEIVE_RX_HEX = RECEIVE_RX_DECODE.hex()
            DATA = self.SEND_DATA.text()
            self.TEXT_EDIT.insertPlainText("原始数据：" + str(RECEIVE_RX_DECODE))
            self.TEXT_EDIT.insertPlainText("解析数据：" + str(RECEIVE_RX_HEX))

            if RECEIVE_RX_DECODE != "":
                SIGNAL_VALUE_LIST = re.findall(".{2}", RECEIVE_RX_HEX)  # 转化为列表
                SIGNAL_VALUE = " ".join(SIGNAL_VALUE_LIST)  # 转换为字符串
                self.RETURN_DATA.setText(SIGNAL_VALUE)
                VALUE_DATA = ""
                if DATA[4] == '3':
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[6:-4]
                elif DATA[4] == '6':
                    if DATA[10] == '8':
                        VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[8:-4]
                    else:
                        VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[10:-4]

                INT_VALUE = int(VALUE_DATA, 16)  # 16->10 进制

                return INT_VALUE

        else:
            pass

    '''
    # 接收报文
    def RECEIVE_MESSAGE(self):
        DATA = self.SEND_DATA.text()

        RECEIVE_RX_DECODE = None
        ser = self.SERIAL_OPEN()
        if ser != None :
            RECEIVE_RX_DECODE = ser.readline().decode("UTF-8")  # 编码器反馈的十六进制字节
        else : pass
        if RECEIVE_RX_DECODE != '':
            SIGNAL_VALUE = RECEIVE_RX_DECODE.replace("\\x", " ")  # 数据部分，并去掉空格
            self.RETURN_DATA.setText(SIGNAL_VALUE)
            VALUE_DATA = ""
            if DATA[4] == '3' :
                VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[6:-4]
            elif DATA[4] == '6' :
                if DATA[10] == '8' :
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[8:-4]
                else :
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[10:-4]

            INT_VALUE = int(VALUE_DATA, 16)  # 16->10 进制

            return INT_VALUE

        else:
            STR_VALUE = '单圈值：0\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)
            print(QMessageBox.warning(self, '警告', '串口没有接收到数据', QMessageBox.Yes, QMessageBox.Yes))
    # 接收报文
    def RECEIVE_MUTIPLE_MESSAGE(self):
        DATA = self.SEND_DATA.text()
        ser = self.SERIAL_OPEN()
        RECEIVE_RX_DECODE = ser.readline()  # 编码器反馈的十六进制字节
        RECEIVE_RX_HEX = RECEIVE_RX_DECODE.hex()
        if RECEIVE_RX_DECODE != '':

            SIGNAL_VALUE_LIST = re.findall(".{2}", RECEIVE_RX_HEX)  # 转化为列表
            SIGNAL_VALUE = " ".join(SIGNAL_VALUE_LIST)  # 转换为字符串
            self.RETURN_DATA.setText(SIGNAL_VALUE)
            VALUE_DATA = ""
            if DATA[4] == '3':
                VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[6:-4]
            elif DATA[4] == '6':
                if DATA[10] == '8':
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[8:-4]
                else:
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[10:-4]

            INT_VALUE = int(VALUE_DATA, 16)  # 16->10 进制

            return INT_VALUE

        else:
            STR_VALUE = '单圈值：0\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)
            print(QMessageBox.warning(self, '警告', '串口没有接收到数据', QMessageBox.Yes, QMessageBox.Yes))
    '''
    # 单圈值
    def SIGNAL_VALUE_FUNCTION(self):
        INT_SIGNAL_VALUE = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0 :
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else :
            # 基础报文
            READ_ENCODER_VALUE = '01 03 00 00 00 01'    # 指令
            self.SEND_MESSAGE(READ_ENCODER_VALUE)
            INT_VALUE = self.SEND_MESSAGE(READ_ENCODER_VALUE)
            STR_VALUE = '单圈值：' + str(INT_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText("解算值->" + STR_VALUE)
            self.SINGAL_VALUE.setText(str(INT_VALUE))

            AGLE = (int(INT_VALUE) / 1024) * 360
            AGLE_SAVE2 = round(AGLE, 2)
            self.AGLE_VALUE.setText(str(AGLE_SAVE2))
    # 多圈值
    def MUTIPLE_VALUE_FUNCTION(self):
        INT_MUTIPLE_VALLUE = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:
            READ_ENCODER_MULTI_TRUN_VALUE = '01 03 00 00 00 02'     # 指令
            INT_MUTIPLE_VALUE = self.SEND_MESSAGE(READ_ENCODER_MULTI_TRUN_VALUE)
            STR_VALUE = '多圈值：' + str(INT_MUTIPLE_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText("解算值->" + STR_VALUE)
            self.MUTIPLE_VALUE.setText(str(INT_MUTIPLE_VALLUE))

    # 圈数值
    def COIL_VALUE_FUNCTION(self):
        INT_COIL_VALUE = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:

            READ_ENCODER_COIL_VALUE = '01 03 00 02 00 01'  # 指令
            INT_COIL_VALUE = self.SEND_MESSAGE(READ_ENCODER_COIL_VALUE)
            self.COIL_VALUE.setText(str(INT_COIL_VALUE))
            STR_VALUE = '圈数：' + str(INT_COIL_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText("解算值->" + STR_VALUE)
    # 设置编码器波特率  ->  04:波特率115200
    def BAUD_VALUE_FUNCTION(self):
        INT_BAUD_VALUE = 0
        BRAUT_RATE = self.BRAUD_RATE.currentText()
        if BRAUT_RATE == '9600' :
            END_STR = '00'
        elif BRAUT_RATE == '19200' :
            END_STR = '01'
        elif BRAUT_RATE == '38400' :
            END_STR = '02'
        elif BRAUT_RATE == '57600' :
            END_STR = '03'
        else :
            END_STR = '04'
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:
            SET_ENCODER_BAUD_RATE = '01 06 00 04 00 ' + END_STR  # 指令
            INT_BAUD_VALUE = self.SEND_MESSAGE(SET_ENCODER_BAUD_RATE)

            if INT_BAUD_VALUE == 0 :    INT_BAUD_VALUE = 9600
            elif INT_BAUD_VALUE == 1 :  INT_BAUD_VALUE = 19200
            elif INT_BAUD_VALUE == 2 :  INT_BAUD_VALUE = 38400
            elif INT_BAUD_VALUE == 3 :  INT_BAUD_VALUE = 57600
            elif INT_BAUD_VALUE == 4 :  INT_BAUD_VALUE = 115200
            else : pass
            STR_VALUE = '波特率：' + str(INT_BAUD_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText("解算值->" + STR_VALUE)

    # 设置零点
    def SET_ZERO_VALUE_FUNCTION(self):
        INT_ZERO_VALUE = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:

            SET_ENCODER_ZEROPOINT = '01 06 00 08 00 01'  # 指令
            INT_ZERO_VALUE = self.SEND_MESSAGE(SET_ENCODER_ZEROPOINT)
            if INT_ZERO_VALUE == 1 :
                INT_ZERO_VALUE = 0
            else : pass

            STR_VALUE = '置零：' + str(INT_ZERO_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText("解算值->" + STR_VALUE)
    # 设置旋转方向 -> 00:顺时针 01:逆时针
    def SET_DIRECTION_FUNCTION(self):
        INT_SET_DIRECTION = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:
            SET_ENCODER_ROLL_DIRECTION = '01 06 00 09 00 00'  # 指令
            INT_SET_DIRECTION = self.SEND_MESSAGE(SET_ENCODER_ROLL_DIRECTION)

            if INT_SET_DIRECTION == 0 :     INT_SET_DIRECTION = '顺时针'
            elif INT_SET_DIRECTION == 1 :   INT_SET_DIRECTION = '逆时针'
            else : pass

            STR_VALUE = '旋转方向：' + str(INT_SET_DIRECTION) + '\r\n'
            self.TEXT_EDIT.insertPlainText("解算值->" + STR_VALUE)

if __name__ == '__main__':
    app = QApplication(argv)
    MainWindow = Modbus_Channel()
    MainWindow.show()
    exit(app.exec_())
